package com.example.demo.mqsever.datacenter;

import com.example.demo.common.BinaryTool;
import com.example.demo.common.MqException;
import com.example.demo.mqsever.core.MSGQueue;
import com.example.demo.mqsever.core.Message;

import java.io.*;
import java.sql.SQLOutput;
import java.util.LinkedList;
import java.util.RandomAccess;
import java.util.Scanner;

// 管理消息在文件中的储存
public class MessageFileManager {
    // 内部类表示该队列的具体统计信息
    static public class Stat{
        public int totalCount;  //总消息数量
        public int validCount;  //有效消息数量
    }

    public void init(){
        // 保持代码风格统一 ~
        // 后续可以扩展
    }

    //获取指定队列对应的消息文件所在路径
    private String getQueueDir(String queueName){
        return "./data/" + queueName;
    }

    //获取该队列的消息数据文件路径
    private String getQueueDataPath(String queueName){
        return getQueueDir(queueName)+"/queue_data.txt";
    }


    //获取该队列的消息统计文件路径
    private String getQueueStatPath(String queueName){
        return getQueueDir(queueName) + "/queue_stat.txt";
    }

    // 创建队列对应的文件和目录
    public void createQueueFiles(String queueName) throws IOException {
        // 创建对应的消息目录
        File baseDir = new File(getQueueDir(queueName));
        if (!baseDir.exists()){
            boolean ok = baseDir.mkdirs();
            if(!ok){
                throw new IOException("创建目录失败！ baseDir=" + baseDir.getAbsolutePath());
            }
        }

        // 创建队列数据文件
        File  queueDataFile = new File(getQueueDataPath(queueName));
        if (!queueDataFile.exists()){
            boolean ok = queueDataFile.createNewFile();
            if(!ok){
                throw new IOException("创建文件失败！ queueDataFile=" + queueDataFile.getAbsolutePath());
            }
        }

        //创建消息统计文件
        File  queueStatFile = new File(getQueueStatPath(queueName));
        if (!queueStatFile.exists()){
            boolean ok = queueStatFile.createNewFile();
            if(!ok){
                throw new IOException("创建文件失败！ queueStatFile=" + queueStatFile.getAbsolutePath());
            }
        }

        // 给消息统计文件，设置初始值
        Stat stat = new Stat();
        stat.validCount = 0;
        stat.totalCount = 0;
        writeStat(queueName,stat);
    }

    // 读取统计文件内容
    private Stat readStat(String queueName){
        Stat stat = new Stat();
        // 文本格式利用 Scanner 直接读取
        try (InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))){
            Scanner scanner = new Scanner(inputStream);
            //统计文件格式：总消息数 /t 有效消息数
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 往统计文件中更新统计数据
    private void writeStat(String queueName,Stat stat){
        try(OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))){
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            //缓存区刷新到硬盘
            printWriter.flush();
        }  catch (IOException e) {
            e.printStackTrace();
        }
    }

    //删除队列的目录和文件
    public void destroyQueueFiles(String queueName) throws IOException {
        File queueDataFile = new File(getQueueDataPath(queueName));
        boolean ok1 = queueDataFile.delete();
        File queueStatFile = new File(getQueueStatPath(queueName));
        boolean ok2 = queueStatFile.delete();
        // 先删除文件，再删除目录（删目录之前要保证目录里为空）
        File baseDir = new File(getQueueDir(queueName));
        boolean ok3 = baseDir.delete();
        if (!ok1 || !ok2 || !ok3){
             throw new IOException("删除失败！ baseDir=" + baseDir.getAbsolutePath());
        }
    }

    //检查队列的文件是否存在
    public boolean checkFilesExits(String queueName){
        //
        File  queueStatFile = new File(getQueueStatPath(queueName));
        if (!queueStatFile.exists()){
            return false;
        }

        File  queueDataFile = new File(getQueueDataPath(queueName));
        if (!queueDataFile.exists()){
           return false;
        }
        return true;
    }

    // 把新的消息放到队列对应的文件中
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {
        // 检查队列是否存在
        if (!checkFilesExits(queue.getName())){
            throw new MqException("[MessageFileManager] 队列对应的文件不存在！ queueName=" + queue.getName());
        }
        // Message 序列化
        byte[] messageBinary = BinaryTool.toBytes(message);
        // 加锁 避免冲突
        synchronized (queue){
            // 获取队列文件长度，计算 Message 的 offsetBeg（文件长度 + 4） 和 offsetEnd（文件长度 + 4 + 自身长度）
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            message.setOffsetBeg(queueDataFile.length() + 4);
            message.setOffsetEnd(queueDataFile.length() + 4 + messageBinary.length);
            // 写入消息的数据文件,true 参数表示追加型文件写
            try(OutputStream outputStream = new FileOutputStream(queueDataFile,true)) {
                // 先写消息长度，长度为四字节
                // outputStream.write(); 一次只写一个字节，不符合要求
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    dataOutputStream.writeInt(messageBinary.length);
                    // 写入消息本体
                    dataOutputStream.write(messageBinary);
                }
            }
            //更新统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount += 1;
            stat.validCount += 1;
            writeStat(queue.getName(),stat);
        }
    }

    // 删除消息（逻辑删除,isValid 设置成0）
    public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException {
        synchronized (queue){
            // 从文件中读取对应的 Message 数据
            try(RandomAccessFile randomAccessFile = new
                    RandomAccessFile(getQueueDataPath(queue.getName()),"rw")){
                byte[] bufferSrc = new byte[(int)(message.getOffsetEnd() - message.getOffsetBeg())];
                randomAccessFile.seek(message.getOffsetBeg());    //光标移动
                randomAccessFile.read(bufferSrc);          // 从当前光标开始的数据内容读入到 bufferSrc 中
                // 读出来的二进制数据，转换为 Message 对象
                Message diskMessage = (Message) BinaryTool.fromByte(bufferSrc);
                // isValid 设置为 0
                diskMessage.setIsValid((byte) 0x0);
                // 重新写入
                byte[] bufferDest = BinaryTool.toBytes(diskMessage);
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(bufferDest);
            }
            // 更新他统计文件
            Stat stat = readStat(queue.getName());
            if (stat.validCount > 0){
                stat.validCount -= 1;
            }
            writeStat(queue.getName(),stat);
        }
    }

    // 从文件读取消息内容加载到内存中（链表表示）
    // 主要用于程序启动时调用
    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {
        LinkedList<Message> messages = new LinkedList<>();
        try(InputStream inputStream = new FileInputStream(getQueueDataPath(queueName))){
            try(DataInputStream dataInputStream = new DataInputStream(inputStream)){
                // 记录当前文件位置的光标
                long currentOffset = 0;
                // 循环读取每一个消息
                while(true){
                    // 1.读取当前消息的长度
                    int messageSize = dataInputStream.readInt();
                    // 2.按照长度读取消息内容
                    byte[] buffer = new byte[messageSize];
                    int actualSize = dataInputStream.read(buffer);
                    if (messageSize != actualSize){
                        // 要读取的长度与实际长度不同（eg：文件内存不够）
                        throw new MqException("[MessageFileManager] 文件格式错误！ queueName=" + queueName);
                    }
                    // 3.反序列化回 Message 对象
                    Message message = (Message) BinaryTool.fromByte(buffer);
                    // 4.判定消息对象是否无效
                    if (message.getIsValid() != 0x1){
                        currentOffset += (4 + messageSize);
                        continue;
                    }
                    // 5.有效数据需要添加到链表中
                    message.setOffsetBeg(currentOffset + 4);
                    message.setOffsetEnd(currentOffset + 4 +messageSize);
                    currentOffset +=(4 + messageSize);
                    messages.add(message);
                }
            } catch (EOFException e) {
                // 文件读到文末会被抛出该异常
                System.out.println("[MessageFileManager] 恢复 Message 数据完成！");
            }
        }
        return messages;
    }

    // 检查是否需要进行GC
    public boolean checkGC(String queueName){
        // 判断
        Stat stat = readStat(queueName);
        if (stat.totalCount > 2000 && (double)stat.validCount / (double) stat.totalCount < 0.5){
            return true;
        }
        return false;
    }

    // GC操作使用复制算法，会创建一个新的文件出来，这里约定新文件的位置
    public String getQueueDataNewPath(String queueName){
        return getQueueDir(queueName) + "/queue_data_new.txt";
    }

    // 垃圾回收机制
    public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {
        synchronized (queue){
            // 统计花费的时间
            long gcBeg = System.currentTimeMillis();

            // 1.创建新文件
            File queueDataNewFile = new File(getQueueDataNewPath(queue.getName()));
            if (queueDataNewFile.exists()){
                throw new MqException("[MessageFileManager] gc 时发现该队列queue_data_new 已经存在");
            }
            boolean ok = queueDataNewFile.createNewFile();
            if (!ok){
                throw new MqException("[MessageFileManager] 创建文件失败 queueName=" + queueDataNewFile.getAbsolutePath());
            }
            // 2.读取有效消息
            LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());
            // 3.将有效消息写入文件
            try(OutputStream outputStream = new FileOutputStream(queueDataNewFile)){
                try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)){
                    for(Message message : messages){
                        byte[] buffer = BinaryTool.toBytes(message);
                        dataOutputStream.writeInt(buffer.length);
                        dataOutputStream.write(buffer);
                    }
                }
            }

            // 4.删除旧的文件
            File queueDataOldFile = new File(getQueueDataPath(queue.getName()));
            ok = queueDataOldFile.delete();
            if (!ok){
                throw new MqException("[MessageFileManager] 删除旧的文件内容失败 queueDataOldFile="
                + queueDataOldFile.getAbsolutePath());
            }

            // 5.重命名
            ok =  queueDataNewFile.renameTo(queueDataOldFile);
            if (!ok){
                throw new MqException("[MessageFileManager] 文件重命名失败 queueDataNewFile=" + queueDataNewFile.getAbsolutePath()
                + ", queueDataOldFile=" + queueDataOldFile.getAbsolutePath());
            }

            //6.更新统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount = messages.size();
            stat.validCount = messages.size();
            writeStat(queue.getName(),stat);
            long gcEnd = System.currentTimeMillis();
            System.out.println("[MessageFileManager] gc 执行完毕！ queueName=" + queue.getName()
                        + "time=" + (gcEnd - gcBeg) + "ms");
        }
    }
}
